﻿/*
 * @author: wizardc
 */

using System.Collections.Generic;
using Dou.GPU.Instancing;
using Dou.Utils;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;

namespace Dou.GPU
{
    /// <summary>
    /// GPUInstancing 绘制管理器
    /// </summary>
    public unsafe partial class GPUInstancingMgr
    {
        private static GPUInstancingMgr _instance;
        public static GPUInstancingMgr instance => _instance ??= new GPUInstancingMgr();

        private int _curBatchId = 0;
        private Dictionary<int, GPUBatch> _gpuBatchMap = new Dictionary<int, GPUBatch>();
        
        private GPUInstancingMgr()
        {
            Recyclable.Register<GPUBatchData>(100, () => new GPUBatchData());
        }

        /// <summary>
        /// 注册一个可一起提交的批次
        /// </summary>
        /// <param name="maximum">当前批次最多可提交的数量，超过该数量的对象会被拆分为多次提交</param>
        /// <param name="castShadows">是否投射阴影</param>
        /// <param name="receiveShadows">是否接收阴影</param>
        /// <param name="layer">绘制到的层</param>
        /// <param name="camera">绘制到的摄像机</param>
        /// <returns>批次ID</returns>
        public int RegisterBatch(Mesh mesh, Material material, int maximum = 256, ShadowCastingMode castShadows = ShadowCastingMode.Off, bool receiveShadows = false, int layer = 0, Camera camera = null)
        {
            var batchId = _curBatchId++;
            var batch = new GPUBatch();
            batch.Init(mesh, material, maximum, castShadows, receiveShadows, layer, camera);
            _gpuBatchMap.Add(batchId, batch);
            return batchId;
        }

        /// <summary>
        /// 注销一个可一起提交的批次
        /// </summary>
        public bool UnregisterBatch(int batchId)
        {
            var batch = GetBatch(batchId);
            if (batch != null)
            {
                batch.Dispose();
                _gpuBatchMap.Remove(batchId);
                return true;
            }
            return false;
        }

        private GPUBatch GetBatch(int batchId)
        {
            if (_gpuBatchMap.ContainsKey(batchId))
            {
                return _gpuBatchMap[batchId];
            }
            return null;
        }

        /// <summary>
        /// 注册一个材质属性
        /// </summary>
        /// <param name="stride">一个数据占用的字节大小</param>
        public void RegisterProperty(int batchId, int nameId, int stride)
        {
            var batch = GetBatch(batchId);
            batch?.RegisterProperty(nameId, stride);
        }

        /// <summary>
        /// 获取一个材质属性
        /// </summary>
        public GPUPropertyData GetProperty(int batchId, int nameId)
        {
            var batch = GetBatch(batchId);
            return batch?.GetProperty(nameId);
        }
        
        #region 提供常见数据类型设置方法

        #region Int
        
        public void SetInt(int batchId, int nameId, int[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetInt(nameId, data);
        }

        public void SetInt(int batchId, int nameId, List<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetInt(nameId, data);
        }

        public void SetInt(int batchId, int nameId, ref NativeArray<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetInt(nameId, ref data);
        }

        public void SetInt(int batchId, int nameId, ref NativeList<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetInt(nameId, ref data);
        }

        public void SetInt(int batchId, int nameId, int* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetInt(nameId, ptr, count);
        }
        
        public void AppendInt(int batchId, int nameId, int[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendInt(nameId, data);
        }

        public void AppendInt(int batchId, int nameId, List<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendInt(nameId, data);
        }

        public void AppendInt(int batchId, int nameId, ref NativeArray<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendInt(nameId, ref data);
        }

        public void AppendInt(int batchId, int nameId, ref NativeList<int> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendInt(nameId, ref data);
        }

        public void AppendInt(int batchId, int nameId, int* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendInt(nameId, ptr, count);
        }

        public void RemoveRangeInt(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeInt(nameId, startIndex, count);
        }
        
        #endregion
        
        #region Float

        public void SetFloat(int batchId, int nameId, float[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat(nameId, data);
        }

        public void SetFloat(int batchId, int nameId, List<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat(nameId, data);
        }

        public void SetFloat(int batchId, int nameId, ref NativeArray<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat(nameId, ref data);
        }

        public void SetFloat(int batchId, int nameId, ref NativeList<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat(nameId, ref data);
        }

        public void SetFloat(int batchId, int nameId, float* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat(nameId, ptr, count);
        }
        
        public void AppendFloat(int batchId, int nameId, float[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat(nameId, data);
        }

        public void AppendFloat(int batchId, int nameId, List<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat(nameId, data);
        }

        public void AppendFloat(int batchId, int nameId, ref NativeArray<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat(nameId, ref data);
        }

        public void AppendFloat(int batchId, int nameId, ref NativeList<float> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat(nameId, ref data);
        }

        public void AppendFloat(int batchId, int nameId, float* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat(nameId, ptr, count);
        }

        public void RemoveRangeFloat(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeFloat(nameId, startIndex, count);
        }

        #endregion

        #region Float2

        public void SetFloat2(int batchId, int nameId, float2[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat2(nameId, data);
        }

        public void SetFloat2(int batchId, int nameId, List<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat2(nameId, data);
        }

        public void SetFloat2(int batchId, int nameId, ref NativeArray<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat2(nameId, ref data);
        }

        public void SetFloat2(int batchId, int nameId, ref NativeList<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat2(nameId, ref data);
        }

        public void SetFloat2(int batchId, int nameId, float2* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat2(nameId, ptr, count);
        }
        
        public void AppendFloat2(int batchId, int nameId, float2[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat2(nameId, data);
        }

        public void AppendFloat2(int batchId, int nameId, List<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat2(nameId, data);
        }

        public void AppendFloat2(int batchId, int nameId, ref NativeArray<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat2(nameId, ref data);
        }

        public void AppendFloat2(int batchId, int nameId, ref NativeList<float2> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat2(nameId, ref data);
        }

        public void AppendFloat2(int batchId, int nameId, float2* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat2(nameId, ptr, count);
        }

        public void RemoveRangeFloat2(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeFloat2(nameId, startIndex, count);
        }

        #endregion

        #region Float3

        public void SetFloat3(int batchId, int nameId, float3[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat3(nameId, data);
        }

        public void SetFloat3(int batchId, int nameId, List<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat3(nameId, data);
        }

        public void SetFloat3(int batchId, int nameId, ref NativeArray<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat3(nameId, ref data);
        }

        public void SetFloat3(int batchId, int nameId, ref NativeList<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat3(nameId, ref data);
        }

        public void SetFloat3(int batchId, int nameId, float3* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat3(nameId, ptr, count);
        }
        
        public void AppendFloat3(int batchId, int nameId, float3[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat3(nameId, data);
        }

        public void AppendFloat3(int batchId, int nameId, List<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat3(nameId, data);
        }

        public void AppendFloat3(int batchId, int nameId, ref NativeArray<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat3(nameId, ref data);
        }

        public void AppendFloat3(int batchId, int nameId, ref NativeList<float3> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat3(nameId, ref data);
        }

        public void AppendFloat3(int batchId, int nameId, float3* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat3(nameId, ptr, count);
        }

        public void RemoveRangeFloat3(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeFloat3(nameId, startIndex, count);
        }

        #endregion
        
        #region Float4

        public void SetFloat4(int batchId, int nameId, float4[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4(nameId, data);
        }

        public void SetFloat4(int batchId, int nameId, List<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4(nameId, data);
        }

        public void SetFloat4(int batchId, int nameId, ref NativeArray<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4(nameId, ref data);
        }

        public void SetFloat4(int batchId, int nameId, ref NativeList<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4(nameId, ref data);
        }

        public void SetFloat4(int batchId, int nameId, float4* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4(nameId, ptr, count);
        }
        
        public void AppendFloat4(int batchId, int nameId, float4[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4(nameId, data);
        }

        public void AppendFloat4(int batchId, int nameId, List<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4(nameId, data);
        }

        public void AppendFloat4(int batchId, int nameId, ref NativeArray<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4(nameId, ref data);
        }

        public void AppendFloat4(int batchId, int nameId, ref NativeList<float4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4(nameId, ref data);
        }

        public void AppendFloat4(int batchId, int nameId, float4* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4(nameId, ptr, count);
        }

        public void RemoveRangeFloat4(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeFloat4(nameId, startIndex, count);
        }

        #endregion

        #region Float4x4

        public void SetFloat4x4(int batchId, int nameId, float4x4[] data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4x4(nameId, data);
        }

        public void SetFloat4x4(int batchId, int nameId, List<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4x4(nameId, data);
        }

        public void SetFloat4x4(int batchId, int nameId, ref NativeArray<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4x4(nameId, ref data);
        }

        public void SetFloat4x4(int batchId, int nameId, ref NativeList<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4x4(nameId, ref data);
        }

        public void SetFloat4x4(int batchId, int nameId, float4x4* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.SetFloat4x4(nameId, ptr, count);
        }
        
        public void AppendFloat4x4(int batchId, int nameId, float4x4[] data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4x4(nameId, data);
        }

        public void AppendFloat4x4(int batchId, int nameId, List<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4x4(nameId, data);
        }

        public void AppendFloat4x4(int batchId, int nameId, ref NativeArray<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4x4(nameId, ref data);
        }

        public void AppendFloat4x4(int batchId, int nameId, ref NativeList<float4x4> data)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4x4(nameId, ref data);
        }

        public void AppendFloat4x4(int batchId, int nameId, float4x4* ptr, int count)
        {
            var batch = GetBatch(batchId);
            batch?.AppendFloat4x4(nameId, ptr, count);
        }

        public void RemoveRangeFloat4x4(int batchId, int nameId, int startIndex, int count)
        {
            var batch = GetBatch(batchId);
            batch?.RemoveRangeFloat4x4(nameId, startIndex, count);
        }

        #endregion

        #endregion

        /// <summary>
        /// 进行一次绘制
        /// </summary>
        public void Draw()
        {
            if (_gpuBatchMap.Count == 0)
            {
                return;
            }

            foreach (var kv in _gpuBatchMap)
            {
                var batch = kv.Value;
                batch.Draw();
            }
        }

        /// <summary>
        /// 清理所有数据
        /// </summary>
        public void Clear()
        {
            foreach (var kv in _gpuBatchMap)
            {
                var batch = kv.Value;
                batch.Dispose();
            }
            _gpuBatchMap.Clear();
        }
    }
}
